<?php
/*
 * @Author: 孙开源 && sunkaiyuan@namenode.cn
 * @Date: 2023-04-12 16:45:39
 * @LastEditors: 孙开源 && sunkaiyuan@namenode.cn
 * @LastEditTime: 2023-04-18 15:22:55
 * @Description: 
 * 
 */
namespace zhijingfeisuo\Kernel;

use GuzzleHttp\Client as GuzzleClient;
use zhijingfeisuo\Kernel\Traits\HasHttpRequests;
use GuzzleHttp\Middleware;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;

class Client
    {
    use HasHttpRequests {
        request as performRequest;
        }
    /**
     * @var \zhijingfeisuo\Kernel\ClientConfig
     */
    protected $config;

    /**
     * @var
     */
    protected $uriVersion = 'base_uri';


    public function setVersion($uriVersion)
        {
        $this->uriVersion = $uriVersion;
        return $this;
        }

    public $app;
    /**
     * Client constructor.
     *
     * @param \zhijingfeisuo\Application
     */
    public function __construct($app)
        {
        $this->app = $app;

        $this->config = $this->normalizeConfig($this->app->config->toArray());

        $this->setVersion($this->uriVersion)->withMiddwares();
        }

    /**
     * @param $name
     *
     * @return mixed
     */
    public function __get($name)
        {
        return $this->app[$name];
        }

    /**
     * GET request.
     *
     * @param string $url
     * @param array  $query
     *
     * @throws \GuzzleHttp\Exception\GuzzleException
     *
     * @return \Psr\Http\Message\ResponseInterface|\zhijingfeisuo\Kernel\Support\Collection|array|object|string
     */
    public function _GET(string $url, array $query = [])
        {
        return $this->request($url, 'GET', ['query' => $query]);
        }


    /**
     * DELETE request.
     *
     * @param string $url
     * @param array  $query
     *
     * @throws \GuzzleHttp\Exception\GuzzleException
     *
     * @return \Psr\Http\Message\ResponseInterface|\zhijingfeisuo\Kernel\Support\Collection|array|object|string
     */
    public function _DELETE(string $url, array $query = [])
        {
        return $this->request($url, 'DELETE', ['query' => $query]);
        }


    /**
     * PUT request.
     *
     * @param string $url
     * @param array  $query
     *
     * @throws \GuzzleHttp\Exception\GuzzleException
     *
     * @return \Psr\Http\Message\ResponseInterface|\zhijingfeisuo\Kernel\Support\Collection|array|object|string
     */
    public function _PUT(string $url, array $data = [])
        {
        return $this->request($url, 'PUT', ['form_params' => $data]);
        }
    /**
     * POST request.
     *
     * @param string $url
     * @param array  $data
     *
     * @throws \GuzzleHttp\Exception\GuzzleException
     *
     * @return \Psr\Http\Message\ResponseInterface|\zhijingfeisuo\Kernel\Support\Collection|array|object|string
     */
    public function _POST(string $url, array $data = [])
        {
        return $this->request($url, 'POST', ['form_params' => $data]);
        }

    /**
     * JSON request.
     *
     * @param string       $url
     * @param string|array $data
     * @param array        $query
     *
     * @throws \GuzzleHttp\Exception\GuzzleException
     *
     * @return \Psr\Http\Message\ResponseInterface|\zhijingfeisuo\Kernel\Support\Collection|array|object|string
     */
    public function postJson(string $url, array $data = [], array $query = [])
        {
        return $this->request($url, 'POST', ['query' => $query, 'json' => $data]);
        }

    /**
     * Upload file.
     *
     * @param string $url
     * @param array  $files
     * @param array  $form
     * @param array  $query
     *
     * @throws \GuzzleHttp\Exception\GuzzleException
     *
     * @return \Psr\Http\Message\ResponseInterface|\zhijingfeisuo\Kernel\Support\Collection|array|object|string
     */
    public function upload(string $url, array $files = [], array $form = [], array $query = [])
        {
        $multipart = [];

        foreach ($files as $name => $contents) {
            $contents    = \is_resource($contents) ?: \fopen($contents, 'r');
            $multipart[] = \compact('name', 'contents');
            }

        foreach ($form as $name => $contents) {
            $multipart = array_merge($multipart, $this->normalizeMultipartField($name, $contents));
            }

        return $this->request($url, 'POST', ['query' => $query, 'multipart' => $multipart]);
        }

    /**
     * @param string $uri
     * @param string $method
     * @param array  $options
     * @param bool   $returnRaw
     *
     * @throws \GuzzleHttp\Exception\GuzzleException
     *
     * @return \Psr\Http\Message\ResponseInterface|\zhijingfeisuo\Kernel\Support\Collection|array|object|string
     */
    public function request(string $uri, string $method = 'GET', array $options = [], $returnRaw = false)
        {

        if ($this->config->getBaseUri($this->uriVersion) && $this->config->needAutoTrimEndpointSlash()) {
            $uri = ltrim($uri, '/');
            }

        $response = $this->performRequest($uri, $method, $options);

        return $this->castResponseToType(
            $response,
            $returnRaw ? 'raw' : $this->config->getOption('response_type')
        );
        }

    /**
     * @param string $url
     * @param string $method
     * @param array  $options
     *
     * @throws \GuzzleHttp\Exception\GuzzleException
     *
     * @return array|object|\zhijingfeisuo\Kernel\Support\Collection|\Psr\Http\Message\ResponseInterface|string
     */
    public function requestRaw(string $url, string $method = 'GET', array $options = [])
        {
        return $this->request($url, $method, $options, true);
        }

    /**
     * Return GuzzleHttp\Client instance.
     *
     * @return \GuzzleHttp\ClientInterface
     */
    public function getHttpClient() : \GuzzleHttp\ClientInterface
        {
        if (!$this->httpClient) {
            $this->httpClient = new GuzzleClient($this->config->getOptions($this->uriVersion));
            }

        return $this->httpClient;
        }

    /**
     * @return \zhijingfeisuo\Kernel\ClientConfig
     */
    public function getConfig() : \zhijingfeisuo\Kernel\ClientConfig
        {

        return $this->config;
        }

    /**
     * @param \zhijingfeisuo\Kernel\Config $config
     *
     * @return \zhijingfeisuo\Kernel\Client
     */
    public function setConfig(\zhijingfeisuo\Kernel\ClientConfig $config) : self
        {
        $this->config = $config;

        return $this;
        }

    /**
     * @param string $name
     * @param mixed  $contents
     *
     * @return array
     */
    public function normalizeMultipartField(string $name, $contents)
        {
        $field = [];

        if (!is_array($contents)) {
            return [compact('name', 'contents')];
            }
        foreach ($contents as $key => $value) {
            $key   = sprintf('%s[%s]', $name, $key);
            $field = array_merge($field, is_array($value) ? $this->normalizeMultipartField($key, $value) : [['name' => $key, 'contents' => $value]]);
            }

        return $field;
        }

    /**
     * @param mixed $config
     *
     * @return \zhijingfeisuo\Kernel\Configs\Config
     */
    protected function normalizeConfig($config) : \zhijingfeisuo\Kernel\ClientConfig
        {
        if (\is_array($config)) {
            $config = new \zhijingfeisuo\Kernel\ClientConfig($config);
            }

        if (!($config instanceof \zhijingfeisuo\Kernel\ClientConfig)) {
            throw new \InvalidArgumentException('config must be array or instance of zhijingfeisuo\Kernel\Config.');
            }

        return $config;
        }
    public function withMiddwares()
        {
        $this->withInterceptor();
        $this->withRequestLog();
        $this->withResponseLog();
        }
    /**
     * 访问返回拦截器
     */
    private function withInterceptor()
        {
        return $this->withResponseInterceptor()->withRequestInterceptor();
        }
    //返回日志
    public function withResponseLog()
        {
        return $this->pushMiddleware(Middleware::retry(function ($retries, RequestInterface $request, ResponseInterface $response = null)
            {
            $response = $this->castResponseToType(
                $response
            );
            $this->app->logger->debug("response", $response);
            return false;
            }), 'response_log');
        }
    //访问日志
    public function withRequestLog()
        {
        $this->pushMiddleware(function (callable $handler)
            {
            return function (RequestInterface $request, array $options) use ($handler)
                {

                $this->app->logger->debug("request:" . $request->getMethod(), [
                    'url'  => $request->getUri(),
                    'body' => $request->getBody()->getContents(),
                    'head' => $request->getHeaders()
                ]);
                return $handler($request, $options);
                };
            }, 'request_logger');
        }
    private function withRequestInterceptor()
        {
        $this->pushMiddleware(function (callable $handler)
            {
            return function (RequestInterface $request, array $options) use ($handler)
                {
                $request = $request->withHeader('accessToken', $this->app->access_token->getToken());
                return $handler($request, $options);
                };
            }, 'access_token');

        return $this->pushMiddleware(function (callable $handler)
            {
            return function (RequestInterface $request, array $options) use ($handler)
                {
                $request = $this->interceptor->request($request);
                return $handler($request, $options);
                };
            }, 'withRequestInterceptor');
        }

    private function withResponseInterceptor()
        {
        return $this->pushMiddleware(Middleware::retry(function ($retries, RequestInterface $request, ResponseInterface $response = null)
            {
            return $this->interceptor->response($response, $request, $retries);
            }), 'retry');
        }
    }